/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package com.bigdata.rdf.store;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.UUID;

import org.apache.log4j.Logger;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;

import com.bigdata.rdf.axioms.NoAxioms;
import com.bigdata.rdf.internal.DTE;
import com.bigdata.rdf.internal.InlineLiteralIV;
import com.bigdata.rdf.internal.InlinePrefixedIntegerURIHandler;
import com.bigdata.rdf.internal.InlineSignedIntegerURIHandler;
import com.bigdata.rdf.internal.InlineSuffixedIntegerURIHandler;
import com.bigdata.rdf.internal.InlineURIFactory;
import com.bigdata.rdf.internal.InlineURIHandler;
import com.bigdata.rdf.internal.InlineUUIDURIHandler;
import com.bigdata.rdf.internal.InlineUnsignedIntegerURIHandler;
import com.bigdata.rdf.internal.XSD;
import com.bigdata.rdf.internal.impl.literal.AbstractLiteralIV;
import com.bigdata.rdf.internal.impl.literal.FullyInlineTypedLiteralIV;
import com.bigdata.rdf.internal.impl.literal.IPv4AddrIV;
import com.bigdata.rdf.internal.impl.literal.LiteralArrayIV;
import com.bigdata.rdf.internal.impl.literal.UUIDLiteralIV;
import com.bigdata.rdf.internal.impl.literal.XSDNumericIV;
import com.bigdata.rdf.internal.impl.uri.URIExtensionIV;
import com.bigdata.rdf.model.BigdataLiteral;
import com.bigdata.rdf.model.BigdataStatement;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.rio.StatementBuffer;
import com.bigdata.rdf.sail.BigdataSail;
import com.bigdata.rdf.vocab.BaseVocabularyDecl;
import com.bigdata.rdf.vocab.core.BigdataCoreVocabulary_v20151106;

/**
 * Integration test suite for {@link InlineURIFactory} (the inline IVs are also
 * tested in their own package without the triple store integration).
 * 
 * @author <a href="mailto:mpersonick@users.sourceforge.net">Mike Personick</a>
 */
public class TestInlineURIs extends AbstractTripleStoreTestCase {

    protected static final Logger log = Logger.getLogger(TestInlineURIs.class);

    /**
     * Please set your database properties here, except for your journal file,
     * please DO NOT SPECIFY A JOURNAL FILE. 
     */
    @Override
    public Properties getProperties() {
        
		final Properties props = new Properties(super.getProperties());

        /*
         * Turn off inference.
         */
        props.setProperty(BigdataSail.Options.AXIOMS_CLASS, NoAxioms.class.getName());
        props.setProperty(BigdataSail.Options.TRUTH_MAINTENANCE, "false");
        props.setProperty(BigdataSail.Options.JUSTIFY, "false");
        props.setProperty(BigdataSail.Options.TEXT_INDEX, "false");
        
        return props;
        
    }

    public TestInlineURIs() {
    }

    public TestInlineURIs(final String arg0) {
        super(arg0);
    }
    
    public void testInlineUUIDs() throws Exception {
    	
        /*
         * The bigdata store, backed by a temporary journal file.
         */
        final AbstractTripleStore store = getStore(getProperties());
	  	
		try {

			final BigdataValueFactory vf = store.getValueFactory();

			final BigdataURI uri1 = vf.createURI("urn:uuid:" + UUID.randomUUID().toString());
			final BigdataURI uri2 = vf.createURI("urn:uuid:" + UUID.randomUUID().toString());
			final BigdataURI uri3 = vf.createURI("urn:uuid:foo");

			final StatementBuffer<BigdataStatement> sb = new StatementBuffer<BigdataStatement>(store, 10/* capacity */);

			sb.add(uri1, RDF.TYPE, XSD.UUID);
			sb.add(uri2, RDF.TYPE, XSD.UUID);
			sb.add(uri3, RDF.TYPE, XSD.UUID);
			sb.flush();
			store.commit();

			if (log.isDebugEnabled())
				log.debug(store.dumpStore());

			assertTrue(uri1.getIV().isInline());
			assertTrue(uri2.getIV().isInline());
			assertFalse(uri3.getIV().isInline());

		} finally {
			store.__tearDownUnitTest();
        }
    	
    }

    public void testInlineIPv4s() throws Exception {
        
        /*
         * The bigdata store, backed by a temporary journal file.
         */
        final AbstractTripleStore store = getStore(getProperties());
	  	
		try {     

			final BigdataValueFactory vf = store.getValueFactory();

            final BigdataURI uri1 = vf.createURI("urn:ipv4:10.128.1.2");
            final BigdataURI uri2 = vf.createURI("urn:ipv4:10.128.1.2/24");
            final BigdataURI uri3 = vf.createURI("urn:ipv4:500.425.1.2");
            final BigdataURI uri4 = vf.createURI("urn:ipv4");

            final BigdataLiteral l = vf.createLiteral("10.128.1.2", XSD.IPV4);
            
			final StatementBuffer<BigdataStatement> sb = new StatementBuffer<BigdataStatement>(store, 10/* capacity */);
            
            sb.add(uri1, RDF.TYPE, XSD.IPV4);
            sb.add(uri2, RDF.TYPE, XSD.IPV4);
            sb.add(uri3, RDF.TYPE, XSD.IPV4);
            sb.add(uri4, RDFS.LABEL, l);
            sb.flush();
            store.commit();
            
            if (log.isDebugEnabled())
                log.debug("\n"+store.dumpStore());

            assertTrue(uri1.getIV().isInline());
            assertTrue(uri2.getIV().isInline());
            assertFalse(uri3.getIV().isInline());
            assertFalse(uri4.getIV().isInline());

        } finally {
            store.__tearDownUnitTest();
        }
        
    }
    
    public void testCustomUUIDNamespace() throws Exception {
        
        final Properties props = new Properties(getProperties());
        
        props.setProperty(AbstractTripleStore.Options.VOCABULARY_CLASS, 
                CustomVocab.class.getName());
        props.setProperty(AbstractTripleStore.Options.INLINE_URI_FACTORY_CLASS, 
                CustomInlineURIFactory.class.getName());
        
        /*
         * The bigdata store, backed by a temporary journal file.
         */
        final AbstractTripleStore store = getStore(props);
	  	
		try {
        
                final BigdataValueFactory vf = store.getValueFactory();

                final BigdataURI uri1 = vf.createURI(CUSTOM_NAMESPACE + UUID.randomUUID().toString());
                final BigdataURI uri2 = vf.createURI(CUSTOM_NAMESPACE + UUID.randomUUID().toString());
                final BigdataURI uri3 = vf.createURI(CUSTOM_NAMESPACE + "foo");
    
    			final StatementBuffer<BigdataStatement> sb = new StatementBuffer<BigdataStatement>(store, 10/* capacity */);

    			sb.add(uri1, RDF.TYPE, XSD.UUID);
                sb.add(uri2, RDF.TYPE, XSD.UUID);
                sb.add(uri3, RDF.TYPE, XSD.UUID);
                sb.flush();
                store.commit();
                
                if (log.isDebugEnabled())
                    log.debug(store.dumpStore());

                assertTrue(uri1.getIV().isInline());
                assertTrue(uri2.getIV().isInline());
                assertFalse(uri3.getIV().isInline());
                
        } finally {
            store.__tearDownUnitTest();
        }
        
    }

    public void testSignedInteger() throws Exception {
        uriRoundtripTestCase(
                SIGNED_INT_NAMESPACE + "1", true,//
                SIGNED_INT_NAMESPACE + "-123", true,//
                SIGNED_INT_NAMESPACE + "-123342343214", true,//
                SIGNED_INT_NAMESPACE + "123342343214", true,//
                SIGNED_INT_NAMESPACE + Byte.MAX_VALUE, true,//
                SIGNED_INT_NAMESPACE + Byte.MIN_VALUE, true,//
                SIGNED_INT_NAMESPACE + Short.MAX_VALUE, true,//
                SIGNED_INT_NAMESPACE + Short.MIN_VALUE, true,//
                SIGNED_INT_NAMESPACE + Integer.MAX_VALUE, true,//
                SIGNED_INT_NAMESPACE + Integer.MIN_VALUE, true,//
                SIGNED_INT_NAMESPACE + Long.MAX_VALUE, true,//
                SIGNED_INT_NAMESPACE + Long.MIN_VALUE, true,//
                SIGNED_INT_NAMESPACE + "19223372036854775807", true,//
                SIGNED_INT_NAMESPACE + "foo", false);
    }

    public void testUnsignedInteger() throws Exception {
        uriRoundtripTestCase(UNSIGNED_INT_NAMESPACE + "1", true,//
                UNSIGNED_INT_NAMESPACE + "-123", false,//
                UNSIGNED_INT_NAMESPACE + "-123342343214", false,//
                UNSIGNED_INT_NAMESPACE + "123342343214", true,//
                UNSIGNED_INT_NAMESPACE + Byte.MAX_VALUE, true,//
                UNSIGNED_INT_NAMESPACE + Short.MAX_VALUE, true,//
                UNSIGNED_INT_NAMESPACE + Integer.MAX_VALUE, true,//
                UNSIGNED_INT_NAMESPACE + Long.MAX_VALUE, true,//
                UNSIGNED_INT_NAMESPACE + "19223372036854775807", true,//
                UNSIGNED_INT_NAMESPACE + "foo", false);
    }

    public void testSuffixedInteger() throws Exception {
        uriRoundtripTestCase(SUFFIXED_INT_NAMESPACE + "1-suffix", true,//
                SUFFIXED_INT_NAMESPACE + "1", false,//
                SUFFIXED_INT_NAMESPACE + "foo-suffix", false,//
                SUFFIXED_INT_NAMESPACE + "-suffix", false,//
                SUFFIXED_INT_NAMESPACE + "foo", false);
    }

    public void testPrefixedInteger() throws Exception {
        uriRoundtripTestCase(PREFIXED_INT_NAMESPACE + "prefix-1", true,//
                PREFIXED_INT_NAMESPACE + "1", false,//
                PREFIXED_INT_NAMESPACE + "prefix-foo", false,//
                PREFIXED_INT_NAMESPACE + "prefix-", false,//
                PREFIXED_INT_NAMESPACE + "foo", false);
    }


    private void uriRoundtripTestCase(final Object... options) throws Exception {

    	final Properties props = new Properties(getProperties());
        props.setProperty(AbstractTripleStore.Options.VOCABULARY_CLASS, CustomVocab.class.getName());
        props.setProperty(AbstractTripleStore.Options.INLINE_URI_FACTORY_CLASS,
                CustomInlineURIFactory.class.getName());
        /*
         * The bigdata store, backed by a temporary journal file.
         */
        final AbstractTripleStore store = getStore(props);
	  	
		try {
        
			final BigdataValueFactory vf = store.getValueFactory();
			final ArrayList<BigdataURI> uris = new ArrayList<>();
			{
				final StatementBuffer<BigdataStatement> sb = new StatementBuffer<BigdataStatement>(store,
						10/* capacity */);
				for (int i = 0; i < options.length; i += 2) {
					final BigdataURI uri = vf.createURI((String) options[i]);
					uris.add(uri);
					sb.add(uri, RDF.TYPE, vf.createLiteral("doesn't matter"));
				}
				sb.flush();
				store.commit();

				if (log.isDebugEnabled())
					log.debug(store.dumpStore());
			}

			for (int i = 0, j = 0; i < options.length; i += 2, j++) {

				final Object givenOption = options[i];
				
				final boolean isInline = (Boolean) options[i + 1];

				final BigdataURI uri = uris.get(j);

				assertEquals("String representation different for:  " + givenOption, givenOption, uri.stringValue());

				assertEquals("Inline expectation different for:  " + givenOption, isInline, uri.getIV().isInline());
				
			}
                
        } finally {
            store.__tearDownUnitTest();
        }
    }

    public void testMultipurposeIDNamespace() throws Exception {
        
        final Properties props = new Properties(getProperties());
        
        props.setProperty(AbstractTripleStore.Options.VOCABULARY_CLASS, 
                CustomVocab.class.getName());
        props.setProperty(AbstractTripleStore.Options.INLINE_URI_FACTORY_CLASS, 
                MultipurposeInlineIDFactory.class.getName());
        
        /*
         * The bigdata store, backed by a temporary journal file.
         */
		final AbstractTripleStore store = getStore(props);

		try {

			final BigdataValueFactory vf = store.getValueFactory();

			final BigdataURI uri1 = vf.createURI(CUSTOM_NAMESPACE + UUID.randomUUID().toString());
			final BigdataURI uri2 = vf.createURI(CUSTOM_NAMESPACE + "1");
			final BigdataURI uri3 = vf.createURI(CUSTOM_NAMESPACE + Short.MAX_VALUE);
			final BigdataURI uri4 = vf.createURI(CUSTOM_NAMESPACE + Integer.MAX_VALUE);
			final BigdataURI uri5 = vf.createURI(CUSTOM_NAMESPACE + Long.MAX_VALUE);
			final BigdataURI uri6 = vf.createURI(CUSTOM_NAMESPACE + "2.3");
			final BigdataURI uri7 = vf.createURI(CUSTOM_NAMESPACE + "foo");

			{
				final StatementBuffer<BigdataStatement> sb = new StatementBuffer<BigdataStatement>(store,
						10/* capacity */);
				sb.add(uri1, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri2, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri3, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri4, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri5, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri6, RDF.TYPE, RDFS.RESOURCE);
				sb.add(uri7, RDF.TYPE, RDFS.RESOURCE);
				sb.flush();
				store.commit();

				if (log.isDebugEnabled())
					log.debug(store.dumpStore());
			}

			for (BigdataURI uri : new BigdataURI[] { uri1, uri2, uri3, uri4, uri5, uri6, uri7 }) {

				assertTrue(uri.getIV().isInline());
				
			}
			
			assertEquals(DTE.UUID, uri1.getIV().getDTE());
			assertEquals(DTE.XSDByte, uri2.getIV().getDTE());
			assertEquals(DTE.XSDShort, uri3.getIV().getDTE());
			assertEquals(DTE.XSDInt, uri4.getIV().getDTE());
			assertEquals(DTE.XSDLong, uri5.getIV().getDTE());
			assertEquals(DTE.XSDDouble, uri6.getIV().getDTE());
			assertEquals(DTE.XSDString, uri7.getIV().getDTE());

		} finally {
			store.__tearDownUnitTest();
		}
        
    }
    
    public void testInlineArray() throws Exception {
        
        final Properties props = new Properties(getProperties());
        
        props.setProperty(AbstractTripleStore.Options.VOCABULARY_CLASS, 
                CustomVocab.class.getName());
        props.setProperty(AbstractTripleStore.Options.INLINE_URI_FACTORY_CLASS, 
                InlineArrayFactory.class.getName());
        
        /*
         * The bigdata store, backed by a temporary journal file.
         */
        final AbstractTripleStore store = getStore(props);

        try {

            final BigdataValueFactory vf = store.getValueFactory();

            final Object[] array = new Object[] {
                    UUID.randomUUID(),
                    "1",
                    Short.MAX_VALUE,
                    Integer.MAX_VALUE,
                    Long.MAX_VALUE,
                    "2.3",
                    "foo"
            };

            final StringBuilder sb = new StringBuilder();
            sb.append(ARRAY);
            for (Object o : array) {
                sb.append(o);
                sb.append(':');
            }
            sb.setLength(sb.length()-1);
            
            final BigdataURI uri1 = vf.createURI(sb.toString());

            {
                final StatementBuffer<BigdataStatement> buf = new StatementBuffer<BigdataStatement>(store,
                        10/* capacity */);
                buf.add(uri1, RDF.TYPE, RDFS.RESOURCE);
                buf.flush();
                store.commit();

                if (log.isDebugEnabled())
                    log.debug(store.dumpStore());
            }

            for (BigdataURI uri : new BigdataURI[] { uri1 }) {

                assertTrue(uri.getIV().isInline());
                
            }
            
            assertEquals(DTE.Extension, uri1.getIV().getDTE());
            
            @SuppressWarnings("rawtypes")
			final InlineLiteralIV[] ivs = ((LiteralArrayIV) ((URIExtensionIV) uri1.getIV()).getLocalNameIV()).getIVs();
            
            assertEquals(DTE.UUID, ivs[0].getDTE());
            assertEquals(DTE.XSDByte, ivs[1].getDTE());
            assertEquals(DTE.XSDShort, ivs[2].getDTE());
            assertEquals(DTE.XSDInt, ivs[3].getDTE());
            assertEquals(DTE.XSDLong, ivs[4].getDTE());
            assertEquals(DTE.XSDDouble, ivs[5].getDTE());
            assertEquals(DTE.XSDString, ivs[6].getDTE());

        } finally {
            store.__tearDownUnitTest();
        }
        
    }
    
    

    private static final String CUSTOM_NAMESPACE = "application:id:";
    private static final String SIGNED_INT_NAMESPACE = "http://example.com/int/";
    private static final String UNSIGNED_INT_NAMESPACE = "http://example.com/uint/";
    private static final String SUFFIXED_INT_NAMESPACE = "http://example.com/intsuf/";
    private static final String PREFIXED_INT_NAMESPACE = "http://example.com/intprefix/";
    private static final String PREFIX = "prefix-";
    private static final String SUFFIX = "-suffix";
    private static final String ARRAY = "myapp:array:";
    
    /**
     * Note: Must be public for access patterns.
     */
    public static class CustomVocab extends BigdataCoreVocabulary_v20151106 {
        
        public CustomVocab() {
            super();
        }
        
        public CustomVocab(final String namespace) {
            super(namespace);
        }
        
        @Override
        protected void addValues() {
            super.addValues();
            
            addDecl(new BaseVocabularyDecl(CUSTOM_NAMESPACE));
            addDecl(new BaseVocabularyDecl(SIGNED_INT_NAMESPACE));
            addDecl(new BaseVocabularyDecl(UNSIGNED_INT_NAMESPACE));
            addDecl(new BaseVocabularyDecl(SUFFIXED_INT_NAMESPACE));
            addDecl(new BaseVocabularyDecl(PREFIXED_INT_NAMESPACE));
            addDecl(new BaseVocabularyDecl(ARRAY));
        }        
        
    }
    
    public static class CustomInlineURIFactory extends InlineURIFactory {
        
        public CustomInlineURIFactory() {
            super();
            addHandler(new InlineUUIDURIHandler(CUSTOM_NAMESPACE));
            addHandler(new InlineSignedIntegerURIHandler(SIGNED_INT_NAMESPACE));
            addHandler(new InlineUnsignedIntegerURIHandler(UNSIGNED_INT_NAMESPACE));
            addHandler(new InlineSuffixedIntegerURIHandler(SUFFIXED_INT_NAMESPACE, SUFFIX));
            addHandler(new InlinePrefixedIntegerURIHandler(PREFIXED_INT_NAMESPACE, PREFIX));
        }
        
        
    }

    public static class MultipurposeInlineIDFactory extends InlineURIFactory {
        
        public MultipurposeInlineIDFactory() {
            super();
            addHandler(new MultipurposeInlineIDHandler(CUSTOM_NAMESPACE));
        }
        
    }

    @SuppressWarnings("rawtypes")
    public static class MultipurposeInlineIDHandler extends InlineURIHandler {
        
        public MultipurposeInlineIDHandler(final String namespace) {
            super(namespace);
        }

		@Override
        protected AbstractLiteralIV createInlineIV(final String localName) {
            
            try {
                return new IPv4AddrIV(localName);
            } catch (Exception ex) {
                // ok, not an ip address
            }
            
            try {
                return new UUIDLiteralIV<>(UUID.fromString(localName));
            } catch (Exception ex) {
                // ok, not a uuid
            }
            
            try {
                return new XSDNumericIV(Byte.parseByte(localName));
            } catch (Exception ex) {
                // ok, not a byte
            }
            
            try {
                return new XSDNumericIV(Short.parseShort(localName));
            } catch (Exception ex) {
                // ok, not a short
            }
            
            try {
                return new XSDNumericIV(Integer.parseInt(localName));
            } catch (Exception ex) {
                // ok, not a int
            }
            
            try {
                return new XSDNumericIV(Long.parseLong(localName));
            } catch (Exception ex) {
                // ok, not a long
            }
            
            try {
                return new XSDNumericIV(Double.parseDouble(localName));
            } catch (Exception ex) {
                // ok, not a double
            }

            // just use a UTF encoded string, this is expensive
            return new FullyInlineTypedLiteralIV<BigdataLiteral>(localName);
            
        }
        
    }

	

    public static class InlineArrayFactory extends InlineURIFactory {
        
        public InlineArrayFactory() {
            super();
            addHandler(new InlineArrayHandler(ARRAY));
        }
        
    }

    @SuppressWarnings("rawtypes")
    public static class InlineArrayHandler extends MultipurposeInlineIDHandler {
        
        public InlineArrayHandler(final String namespace) {
            super(namespace);
        }

        @Override
        protected AbstractLiteralIV createInlineIV(final String localName) {

            final List<AbstractLiteralIV> list = new LinkedList<>();
            
            final StringTokenizer st = new StringTokenizer(localName, ":");
            while (st.hasMoreTokens()) {
                list.add(super.createInlineIV(st.nextToken()));
            }
            
            if (list.isEmpty()) {
                throw new IllegalArgumentException();
            }
            
            return new LiteralArrayIV(list.toArray(new AbstractLiteralIV[list.size()]));
            
        }
        
        @Override
        public String getLocalNameFromDelegate(
                final AbstractLiteralIV<BigdataLiteral, ?> delegate) {
            
            final StringBuilder sb = new StringBuilder();
            
            final LiteralArrayIV array = (LiteralArrayIV) delegate;
            
            for (InlineLiteralIV iv : array.getIVs()) {
               sb.append(iv.getInlineValue());
               sb.append(':');
            }
            sb.setLength(sb.length()-1);
            
            return sb.toString();
            
        }
    }

}
